/*
The MIT License (MIT)

Copyright (c) 2013 Mike Dapiran, Brian May, Richard Pospesel, and Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
#include <Windows.h>

#include "Hogshead.Common.h"
#include "Hogshead.Core.h"

struct ID3D11Buffer;

namespace Hogshead
{
	namespace DX11Rendering
	{
		/**
		* This class stores, continguously in memory, data that we want to send to the GPU as a cbuffer.
		* Internal members can be get and set by correct name and type. Type is on the user. You must
		* ensure that 
		*
		* @see ShaderBindFlags
		* @see ConstantBufferGroup
		* 
		* @author Bert Wierenga
		*/
		enum ShaderBindFlags;

		class ConstantBuffer
		{
		public:
			/**
			* Fills out an 'empty' ConstantBuffer
			*/
			ConstantBuffer(void);
			~ConstantBuffer(void);

			/**
			* Creates a ConstantBuffer from another. Deep copy.
			*
			* @param in_other The ConstantBuffer to create ourself from.
			*/
			ConstantBuffer(const ConstantBuffer& in_other);

			/**
			* Copies a ConstantBuffer into this ConstantBuffer.
			*
			* @param in_other The ConstantBuffer to copy to ourself from.
			*/
			ConstantBuffer& operator=(const ConstantBuffer& in_other);

			/**
			* This is used to set an internal variable, you need to know what type it is,
			* and the string name it was given in XML. This will set the _up_to_date flag to false;
			*
			* @param T The primitive type the variable is.
			* @param in_which The string name for the variable.
			* @param in_data The value you would like to set the data to.
			*/
			template<typename T>
			void setInternal(const String& in_which, const T& in_data)
			{
				if(_shell.contains(in_which))
				{
					void* internal_ = _start_address + _shell[in_which];
					T* true_internal = (T*) internal_;
					*true_internal = in_data;

					//set the flag that it has been changed
					_up_to_date = false;
				}
				else
				{
					error("Attempting to lookup an internal that does not exist.");
				}
			}

			template<typename T>
			void setInternalArrayElement(const String& in_which, const T& in_data, int in_index)
			{
				if(_shell.contains(in_which))
				{
					//index into the data
					//1. find the offset where the array starts
					//2. add the offset into the individual array to find that particular element
					void* internal_ = _start_address + _shell[in_which] + (in_index * sizeof(T));
					T* true_internal = (T*) internal_;
					*true_internal = in_data;

					//set the flag that it has been changed
					_up_to_date = false;
				}
				else
				{
					error("Attempting to lookup an internal that does not exist.");
				}
			}

			template<typename T>
			void setInternalArray(const String& in_which, const T* in_data, int in_num_elements)
			{
				if(_shell.contains(in_which))
				{
					void* internal_ = _start_address + _shell[in_which];
					memcpy(internal_, in_data, sizeof(T) * in_num_elements);

					//set the flag that it has been changed
					_up_to_date = false;
				}
				else
				{
					error("Attempting to lookup an internal that does not exist.");
				}
			}


			/**
			* This is used to get an internal variable, you need to know what type it is,
			* and the string name it was given in XML.
			*
			* @param T The primitive type the variable is.
			* @param in_which The string name for the variable.
			*
			* @return Returns an instance of T with the information stored for the requested variable.
			*/
			template<typename T>
			T getInternal(const String& in_which)
			{
				if(_shell.contains(in_which))
				{
					T* internal_ = (T*) (_start_address + _shell[in_which]);
					return *internal_;
				}
				else
				{
					error("Attempting to lookup an internal that does not exist.");
				}
			}

			/**
			* @return Returns which register this ConstantBuffer should be bound to.
			*/
			UINT registerNumber() const;

			/**
			* @return Returns the size of the buffer in Bytes.
			*/
			UINT size() const;

			/**
			* @return Returns the flag for which Shader Stages this buffer should be bound to.
			*/
			ShaderBindFlags shaderBindFlags() const;

			/**
			* @return Returns a constant pointer to a ID3D11Buffer pointer.
			*/
			ID3D11Buffer* const* buffer();

			/**
			* This is called by ConstantBufferGroup whenever it needs a reset.
			*/
			void clear();

			/**
			* This is to be called to create the block of memory this class will internalize. This should
			* be called after some number of times addPrimitive having been called.
			*
			* @see addPrimitive( const String& in_name, UINT in_size_of_primitive )
			*
			* @param in_register The register this ConstantBuffer will be bound to on the GPU.
			* @param in_flags A flag for which Shader stages we want this ConstantBuffer to be bound 
			* to on the GPU.
			*/
			void build( UINT in_register, ShaderBindFlags in_flags );
			
			/**
			* this is used to add another primitive to the ConstantBuffer at creation time only.
			*
			* @param in_name The name we want to refer to this primitive by.
			* @param in_size_of_primitive The size in Bytes of this primitive.
			*/
			void addPrimitive( const String& in_name, UINT in_size_of_primitive );


			bool operator==(const ConstantBuffer&) const;
		private:

			//which register should this one go to
			UINT _register_num;
			//where does the memory we need to copy begin
			byte* _start_address;
			//what size of memory do we need to copy
			UINT _size;
			//which shaders do we want to bind this constant buffer to
			ShaderBindFlags _shader_bind_flags;

			//a hashtable of address offsets from the start address 
			//for look up of internal members
			HashTable<String, UINT> _shell;

			//refernce to our gpu side object
			ID3D11Buffer* _buffer;

			//flag used to update buffer as it is changed
			bool _up_to_date;
		};
	}
}
